Skip to content

fix: reduce redundant updates for handlers and gestures#2188

Merged
Yradex merged 6 commits intolynx-family:mainfrom
Yradex:wt/mts-support-use-callback-20260202
Apr 22, 2026
Merged

fix: reduce redundant updates for handlers and gestures#2188
Yradex merged 6 commits intolynx-family:mainfrom
Yradex:wt/mts-support-use-callback-20260202

Conversation

@Yradex
Copy link
Copy Markdown
Collaborator

@Yradex Yradex commented Feb 3, 2026

Summary by CodeRabbit

  • Bug Fixes
    • Reduced redundant runtime updates when event handlers and gesture objects remain stable across re-renders, decreasing unnecessary native updates
    • Improved gesture removal to reliably clear gesture state on affected elements
    • Prevented unnecessary native updates when prop spreading doesn't semantically change handlers or gestures

Summary

This PR reduces redundant native updates for stable main-thread handlers and gestures by separating cached background-side objects from the payloads committed to the main thread.

The previous flow mixed commit-time runtime metadata (for example _execId) into objects that were also reused for diffing. As a result, a handler or gesture could stay semantically unchanged across rerenders but still look different to the patching layer, which caused unnecessary native updates. This PR moves those paths to copy-on-commit, and also tightens the spread-driven gesture removal flow so cleanup only touches ReactLynx-owned gesture state.

Key points

1. Copy-on-commit for gestures and worklets

The main issue was that commit-time preparation mutated objects that were later reused for diffing. For stable references, that meant the diff saw runtime-only churn instead of a real semantic change.

Before, commit-time preparation effectively behaved like this:

baseGesture.callbacks[name] = onPostWorkletCtx(baseGesture.callbacks[name])!;

That mutates the cached gesture object itself.

Now the runtime prepares a committed copy instead:

const committedCallbacks = { ...baseGesture.callbacks };
committedCallbacks[name] = prepareWorkletForCommit(callback)!;

const committed = {
  ...baseGesture,
  callbacks: committedCallbacks,
};

This keeps the background-side cached object stable while still attaching _execId to the payload sent to the main thread.

2. Spread props only clone when commit-time rewriting is actually needed

Stable rerenders through spread props were still expensive because every changed spread was shallow-cloned and scanned, even when it only contained primitive attrs.

The old shape was effectively:

const committed = { ...spread };
for (const key in committed) {
  // scan and possibly rewrite every key
}
return committed;

Now the spread commit path clones lazily:

let committed;
for (const key in spread) {
  // only clone once we discover a worklet / gesture / timing flag
  committed ??= { ...spread };
}
return committed ?? spread;

That means ordinary spread updates keep the original object and avoid unnecessary allocation/scanning, while worklets and gestures still get rewritten correctly when needed.

3. Gesture removal from spread props now cleans up the right state

When a spread removed main-thread:gesture, the removal happened late in updateSpread(), after surviving keys had already been processed. That made cleanup order important.

A problematic transition looked like this:

{ 'clip-radius': 8, 'main-thread:gesture': g }
-> { 'clip-radius': 8 }

clip-radius still requires flatten={false}, so clearing flatten during gesture cleanup was incorrect. This PR fixes that by only clearing ReactLynx-owned gesture state and leaving unrelated spread state alone.

In addition, when __RemoveGestureDetector is available, the runtime no longer forcefully writes gesture = null itself. That avoids clobbering a user-provided gesture attr if detector cleanup is already owned by the native remove API.

4. Committed gesture serialization no longer depends on mutating the original object

The gesture commit path now serializes the committed copy instead of relying on the original object's toJSON behavior. This makes the runtime more robust when callbacks have been rewritten for commit-time transport and avoids reintroducing _execId churn through serialization.

Conceptually, the committed payload now comes from the committed object itself:

const serialize = () => serializeCommittedGesture(committedGesture);
return {
  ...committedGesture,
  serialize,
  toJSON: serialize,
};

This keeps serialization aligned with the object that actually contains the committed callbacks.

5. Behavioral boundary of this change

This PR is intentionally scoped to runtime-owned commit and cleanup behavior:

  • Stable main-thread handlers and gestures should no longer trigger redundant native patches.
  • Spread rerenders should avoid unnecessary cloning when no runtime rewrite is required.
  • Removing main-thread:gesture from spread props should not clear unrelated state such as flatten.
  • When detector removal can already be handled by __RemoveGestureDetector, ReactLynx should not additionally clobber a user-provided gesture attr.

Checklist

  • Tests updated (or not required).
  • Documentation updated (or not required).
  • Changeset added, and when a BREAKING CHANGE occurs, it needs to be clearly marked (or not required).

@Yradex Yradex requested review from HuJean and hzy as code owners February 3, 2026 07:37
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Feb 3, 2026

🦋 Changeset detected

Latest commit: 0ec04e7

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@lynx-js/react Patch
@lynx-js/react-umd Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 3, 2026

📝 Walkthrough

Walkthrough

This PR optimizes React runtime gesture handling by converting gesture toJSON methods from arrow functions to regular functions with explicit this binding, introducing a commit-preparation pipeline for gestures (prepareGestureForCommit), and improving gesture removal cleanup to prevent redundant native updates when handlers and gestures remain stable across rerenders.

Changes

Cohort / File(s) Summary
Changeset Documentation
.changeset/fix-react-runtime-execid-churn.md
Patch-level changeset documenting runtime behavior fix that reduces redundant updates when main-thread event handlers and gesture objects remain stable, and clarifies gesture removal behavior in spread props.
Gesture Runtime Core
packages/lynx/gesture-runtime/src/baseGesture.ts, packages/lynx/gesture-runtime/src/composition.ts
Converted toJSON from arrow functions to regular function expressions with explicit this: BaseGesture/ComposedGesture parameter, changing this binding from lexical capture to call-site determined.
Gesture Processing Pipeline
packages/react/runtime/src/snapshot/gesture/processGestureBagkround.ts, packages/react/runtime/src/snapshot/gesture/processGesture.ts
Replaced mutating processGestureBackground with new prepareGestureForCommit export that returns commit-ready gesture payloads without mutation; added clearLegacyGestureState helper to conditionally clear legacy gesture attributes during removal based on __RemoveGestureDetector availability.
Test Suites
packages/react/runtime/__test__/snapshot/mtf-execid-churn.test.jsx, packages/react/runtime/__test__/snapshot/gesture.test.jsx, packages/react/runtime/__test__/snapshot/gesture/prepareGestureForCommit.test.js, packages/react/runtime/__test__/snapshot/gesture/processGesture.test.ts
Added comprehensive snapshot and unit tests for execId churn reduction, gesture serialization consistency, commit preparation with non-mutating callbacks, and gesture removal cleanup edge cases including fallback behavior without detector API.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • hzy
  • colinaaa

Poem

🐰 Stable gestures, no execId churn,
The toJSON functions know where to turn,
Commitments prepared, not mutating away,
Redundant updates cleared—cleaner today! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.45% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main objective of the PR: reducing redundant native updates by implementing copy-on-commit for gestures/handlers and avoiding unnecessary spread cloning.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 482ffeaefc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/react/runtime/src/gesture/processGestureBagkround.ts Outdated
@Yradex Yradex force-pushed the wt/mts-support-use-callback-20260202 branch from 482ffea to 77dec4b Compare February 3, 2026 07:42
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@packages/react/runtime/src/gesture/processGestureBagkround.ts`:
- Around line 45-51: The committedCallbacks cloning assumes every callback is
non-null and calls prepareWorkletForCommit(...)! which will throw when a
callback is null/undefined; update the loop over committedCallbacks (the object
derived from baseGesture.callbacks) to check each committedCallbacks[name] for
null/undefined and only call prepareWorkletForCommit when it is present,
otherwise leave the entry as-is (remove the non-null assertions and skip calling
prepareWorkletForCommit for null/undefined values) so null callbacks are
preserved and not spread into worklets.

@codecov
Copy link
Copy Markdown

codecov Bot commented Feb 3, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ All tests successful. No failed tests found.

📢 Thoughts on this report? Let us know!

@Yradex Yradex force-pushed the wt/mts-support-use-callback-20260202 branch from 77dec4b to b09eaa9 Compare February 3, 2026 07:53
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@packages/react/runtime/__test__/snapshot/mtf-execid-churn.test.jsx`:
- Around line 142-199: The test reads lynx.getNativeApp().callLepusMethod too
soon after the background render, causing a race; after render(<Comp tick={1}
/>, __root) in the "rerender with no semantic changes" block, await the
scheduled patch (e.g., await Promise.resolve() or flush pending
timers/microtasks) before switching back to main thread and accessing
lynx.getNativeApp().callLepusMethod.mock.calls so that
getSnapshotPatchFromPatchUpdateCall sees the finalized call.
🧹 Nitpick comments (1)
packages/react/runtime/__test__/snapshot/mtf-execid-churn.test.jsx (1)

12-16: Make helper failures explicit instead of a TypeError.
If call or call[1].data is missing, the current code throws a vague error. Adding explicit assertions makes failures easier to diagnose.

Suggested change
 function getSnapshotPatchFromPatchUpdateCall(call) {
-  const obj = call[1];
-  const parsed = JSON.parse(obj.data);
+  expect(call, 'expected a patch update call').toBeTruthy();
+  const obj = call[1];
+  expect(obj?.data, 'expected patch payload').toBeTypeOf('string');
+  const parsed = JSON.parse(obj.data);
   return parsed.patchList?.[0]?.snapshotPatch;
 }

Comment thread packages/react/runtime/__test__/snapshot/mtf-execid-churn.test.jsx
@relativeci
Copy link
Copy Markdown

relativeci Bot commented Feb 3, 2026

Web Explorer

#9079 Bundle Size — 899.89KiB (+0.13%).

0ec04e7(current) vs ea5e30e main#9077(baseline)

Bundle metrics  Change 2 changes
                 Current
#9079
     Baseline
#9077
No change  Initial JS 44.46KiB 44.46KiB
No change  Initial CSS 2.22KiB 2.22KiB
Change  Cache Invalidation 24.15% 0%
No change  Chunks 9 9
No change  Assets 11 11
No change  Modules 229 229
No change  Duplicate Modules 11 11
Change  Duplicate Code 27.2%(-0.07%) 27.22%
No change  Packages 10 10
No change  Duplicate Packages 0 0
Bundle size by type  Change 1 change Regression 1 regression
                 Current
#9079
     Baseline
#9077
Regression  JS 495.65KiB (+0.24%) 494.47KiB
No change  Other 402.02KiB 402.02KiB
No change  CSS 2.22KiB 2.22KiB

Bundle analysis reportBranch Yradex:wt/mts-support-use-callba...Project dashboard


Generated by RelativeCIDocumentationReport issue

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@packages/react/runtime/__test__/snapshot/mtf-execid-churn.test.jsx`:
- Around line 26-34: The test overrides a global value but never restores it;
capture the original SystemInfo.lynxSdkVersion in the beforeEach (e.g., const
originalLynxSdkVersion = SystemInfo.lynxSdkVersion) before setting
SystemInfo.lynxSdkVersion = '999.999', and then restore it in afterEach (set
SystemInfo.lynxSdkVersion = originalLynxSdkVersion) alongside
vi.restoreAllMocks() and elementTree.clear() to prevent cross-test leakage.
- Around line 20-24: Tests mutate global state via injectUpdateMainThread
(assigning globalThis['rLynxChange']) and patch Preact's options.commit via
replaceCommitHook/hook(), so add an afterAll() that deletes
globalThis['rLynxChange'] and restores options.commit to its original value (use
the original reference captured by hook() or reset options.commit to
undefined/default) in the mtf-execid-churn.test.jsx file so test globals are
cleaned up in addition to vi.restoreAllMocks().

Comment thread packages/react/runtime/__test__/snapshot/mtf-execid-churn.test.jsx
Comment thread packages/react/runtime/__test__/snapshot/mtf-execid-churn.test.jsx
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Feb 3, 2026

Merging this PR will degrade performance by 19.13%

⚡ 3 improved benchmarks
❌ 3 regressed benchmarks
✅ 75 untouched benchmarks
⏩ 26 skipped benchmarks1

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
003-hello-list-destroyBackground 3.4 ms 2.8 ms +23.32%
004-various-update-setAttribute__BatchedValues 338.2 µs 418.2 µs -19.13%
004-various-update__main-thread-setAttribute__MT_Event 91.1 µs 107.8 µs -15.49%
002-hello-reactLynx-destroyBackground 832.6 µs 668.7 µs +24.5%
008-many-use-state-destroyBackground 8 ms 9.2 ms -13.08%
transform 1000 view elements 46.8 ms 40 ms +17.14%

Comparing Yradex:wt/mts-support-use-callback-20260202 (0ec04e7) with main (ea5e30e)2

Open in CodSpeed

Footnotes

  1. 26 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

  2. No successful run was found on main (eec539a) during the generation of this report, so ea5e30e was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

HuJean
HuJean previously approved these changes Feb 3, 2026
@Yradex Yradex marked this pull request as draft February 4, 2026 04:11
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 5, 2026

This pull request has been automatically marked as stale because it has not had recent activity. If this pull request is still relevant, please leave any comment (for example, "bump").

@github-actions github-actions Bot added the stale Inactive for 30 days. Will be closed by bots. label Apr 5, 2026
@Yradex Yradex force-pushed the wt/mts-support-use-callback-20260202 branch from 23f46ef to ab35b6c Compare April 17, 2026 07:20
@relativeci
Copy link
Copy Markdown

relativeci Bot commented Apr 17, 2026

React Example

#7506 Bundle Size — 225.23KiB (+0.36%).

0ec04e7(current) vs ea5e30e main#7504(baseline)

Bundle metrics  Change 2 changes
                 Current
#7506
     Baseline
#7504
No change  Initial JS 0B 0B
No change  Initial CSS 0B 0B
Change  Cache Invalidation 35.05% 0%
No change  Chunks 0 0
No change  Assets 4 4
No change  Modules 179 179
No change  Duplicate Modules 69 69
Change  Duplicate Code 44.57%(+0.13%) 44.51%
No change  Packages 2 2
No change  Duplicate Packages 0 0
Bundle size by type  Change 1 change Regression 1 regression
                 Current
#7506
     Baseline
#7504
No change  IMG 145.76KiB 145.76KiB
Regression  Other 79.47KiB (+1.04%) 78.65KiB

Bundle analysis reportBranch Yradex:wt/mts-support-use-callba...Project dashboard


Generated by RelativeCIDocumentationReport issue

@relativeci
Copy link
Copy Markdown

relativeci Bot commented Apr 17, 2026

React MTF Example

#639 Bundle Size — 196.39KiB (+0.42%).

0ec04e7(current) vs ea5e30e main#637(baseline)

Bundle metrics  Change 2 changes
                 Current
#639
     Baseline
#637
No change  Initial JS 0B 0B
No change  Initial CSS 0B 0B
Change  Cache Invalidation 43.12% 0%
No change  Chunks 0 0
No change  Assets 3 3
No change  Modules 173 173
No change  Duplicate Modules 66 66
Change  Duplicate Code 44.07%(+0.16%) 44%
No change  Packages 2 2
No change  Duplicate Packages 0 0
Bundle size by type  Change 1 change Regression 1 regression
                 Current
#639
     Baseline
#637
No change  IMG 111.23KiB 111.23KiB
Regression  Other 85.15KiB (+0.97%) 84.34KiB

Bundle analysis reportBranch Yradex:wt/mts-support-use-callba...Project dashboard


Generated by RelativeCIDocumentationReport issue

@relativeci
Copy link
Copy Markdown

relativeci Bot commented Apr 17, 2026

React External

#624 Bundle Size — 679.93KiB (+0.76%).

0ec04e7(current) vs ea5e30e main#622(baseline)

Bundle metrics  Change 1 change
                 Current
#624
     Baseline
#622
No change  Initial JS 0B 0B
No change  Initial CSS 0B 0B
Change  Cache Invalidation 39.21% 98.35%
No change  Chunks 0 0
No change  Assets 3 3
No change  Modules 17 17
No change  Duplicate Modules 5 5
No change  Duplicate Code 8.59% 8.59%
No change  Packages 0 0
No change  Duplicate Packages 0 0
Bundle size by type  Change 1 change Regression 1 regression
                 Current
#624
     Baseline
#622
Regression  Other 679.93KiB (+0.76%) 674.83KiB

Bundle analysis reportBranch Yradex:wt/mts-support-use-callba...Project dashboard


Generated by RelativeCIDocumentationReport issue

@github-actions github-actions Bot removed the stale Inactive for 30 days. Will be closed by bots. label Apr 17, 2026
@Yradex Yradex marked this pull request as ready for review April 20, 2026 08:02
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f0675b33c8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/react/runtime/src/gesture/processGesture.ts Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
packages/react/runtime/__test__/snapshot/mtf-execid-churn.test.jsx (2)

166-172: Nit: toJSON destructured binding is unused.

Minor readability nit — rename to _ to signal intent (the goal is only to strip toJSON from rest).

Proposed tweak
-      toJSON() {
-        const { toJSON, ...rest } = this;
+      toJSON() {
+        const { toJSON: _toJSON, ...rest } = this;
         return {
           ...rest,
           __isSerialized: true,
         };
       },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react/runtime/__test__/snapshot/mtf-execid-churn.test.jsx` around
lines 166 - 172, The destructured binding named toJSON inside the toJSON method
is unused and hurts readability; change the destructuring from `{ toJSON,
...rest } = this` to use a throwaway identifier (e.g. `{ _: _, ...rest } = this`
or simply `{ _: , ...rest } = this` depending on style) so it's obvious the
intent is only to remove the toJSON property and keep the remaining fields;
update the toJSON method in the test (the toJSON method on the test
object/class) to destructure the property into a clearly unused name like `_`
and return `{ ...rest, __isSerialized: true }`.

49-215: Optional: extract the shared render/hydrate/rerender harness.

The three it blocks share near-identical main-thread-render → background-render → hydrate → rerender scaffolding; only the component and the assertion target differ. Extracting a helper like runStableRerenderCase(Comp, triggerRerender) would cut ~60 lines and make future churn tests a one-liner. Deferable — current form is readable as-is.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react/runtime/__test__/snapshot/mtf-execid-churn.test.jsx` around
lines 49 - 215, Tests duplicate the same main-thread render → background render
→ hydrate → rerender scaffold across three it blocks; extract a helper (e.g.,
runStableRerenderCase) to remove duplication. Implement
runStableRerenderCase(Comp, triggerRerender) that performs: set __root.__jsx and
renderPage() for main-thread render; switch to background and call render(<Comp
/>, __root); run hydrate by invoking
lynxCoreInject.tt.OnLifecycleEvent(...__OnLifecycleEvent.mock.calls[0]) then
switch to main thread and invoke the recorded callLepusMethod; for rerender
switch to background, clear mocks
(lynx.getNativeApp().callLepusMethod.mockClear()), call triggerRerender (this
can be bump_ or rendering with new props), await waitSchedule(), switch to main
thread and return the rLynxChange so callers can assert
getSnapshotPatchFromPatchUpdateCall(rLynxChange) is undefined; update the three
tests to call runStableRerenderCase with their Comp and appropriate trigger
function.
packages/react/runtime/__test__/snapshot/gesture.test.jsx (1)

1148-1155: Consider also covering user-provided gesture attr preservation.

This regression covers retaining clip-radius/flatten, but the PR objective also calls out not clobbering unrelated user-provided gesture attrs. A small variant with both gesture and main-thread:gesture, then removing only main-thread:gesture, would lock that behavior down too.

Also applies to: 1196-1199

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react/runtime/__test__/snapshot/gesture.test.jsx` around lines 1148
- 1155, Add a test variant that ensures user-provided 'gesture' attributes are
not clobbered when only 'main-thread:gesture' is removed: update the existing
props construction (the variable props used with keepGesture and _gesture) to
include a case where both 'gesture' and 'main-thread:gesture' are present, then
simulate removal of only 'main-thread:gesture' and assert that 'gesture' remains
unchanged; mirror the same change for the other related case referenced (the
analogous code around the other instance of props). Ensure assertions reference
the same props/_gesture symbols so the test verifies preservation of
user-provided 'gesture' attributes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/react/runtime/__test__/snapshot/gesture.test.jsx`:
- Around line 1148-1155: Add a test variant that ensures user-provided 'gesture'
attributes are not clobbered when only 'main-thread:gesture' is removed: update
the existing props construction (the variable props used with keepGesture and
_gesture) to include a case where both 'gesture' and 'main-thread:gesture' are
present, then simulate removal of only 'main-thread:gesture' and assert that
'gesture' remains unchanged; mirror the same change for the other related case
referenced (the analogous code around the other instance of props). Ensure
assertions reference the same props/_gesture symbols so the test verifies
preservation of user-provided 'gesture' attributes.

In `@packages/react/runtime/__test__/snapshot/mtf-execid-churn.test.jsx`:
- Around line 166-172: The destructured binding named toJSON inside the toJSON
method is unused and hurts readability; change the destructuring from `{ toJSON,
...rest } = this` to use a throwaway identifier (e.g. `{ _: _, ...rest } = this`
or simply `{ _: , ...rest } = this` depending on style) so it's obvious the
intent is only to remove the toJSON property and keep the remaining fields;
update the toJSON method in the test (the toJSON method on the test
object/class) to destructure the property into a clearly unused name like `_`
and return `{ ...rest, __isSerialized: true }`.
- Around line 49-215: Tests duplicate the same main-thread render → background
render → hydrate → rerender scaffold across three it blocks; extract a helper
(e.g., runStableRerenderCase) to remove duplication. Implement
runStableRerenderCase(Comp, triggerRerender) that performs: set __root.__jsx and
renderPage() for main-thread render; switch to background and call render(<Comp
/>, __root); run hydrate by invoking
lynxCoreInject.tt.OnLifecycleEvent(...__OnLifecycleEvent.mock.calls[0]) then
switch to main thread and invoke the recorded callLepusMethod; for rerender
switch to background, clear mocks
(lynx.getNativeApp().callLepusMethod.mockClear()), call triggerRerender (this
can be bump_ or rendering with new props), await waitSchedule(), switch to main
thread and return the rLynxChange so callers can assert
getSnapshotPatchFromPatchUpdateCall(rLynxChange) is undefined; update the three
tests to call runStableRerenderCase with their Comp and appropriate trigger
function.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ee8db453-f966-4ca7-b71e-2349b5aa4d0b

📥 Commits

Reviewing files that changed from the base of the PR and between 23f46ef and 6aa232b.

📒 Files selected for processing (9)
  • .changeset/fix-react-runtime-execid-churn.md
  • packages/lynx/gesture-runtime/src/baseGesture.ts
  • packages/lynx/gesture-runtime/src/composition.ts
  • packages/react/runtime/__test__/gesture/prepareGestureForCommit.test.js
  • packages/react/runtime/__test__/gesture/processGesture.test.ts
  • packages/react/runtime/__test__/snapshot/gesture.test.jsx
  • packages/react/runtime/__test__/snapshot/mtf-execid-churn.test.jsx
  • packages/react/runtime/src/gesture/processGesture.ts
  • packages/react/runtime/src/gesture/processGestureBagkround.ts
✅ Files skipped from review due to trivial changes (3)
  • .changeset/fix-react-runtime-execid-churn.md
  • packages/lynx/gesture-runtime/src/baseGesture.ts
  • packages/react/runtime/test/gesture/prepareGestureForCommit.test.js
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/lynx/gesture-runtime/src/composition.ts
  • packages/react/runtime/test/gesture/processGesture.test.ts
  • packages/react/runtime/src/gesture/processGestureBagkround.ts
  • packages/react/runtime/src/gesture/processGesture.ts

Yradex added 3 commits April 22, 2026 16:12
- Implement copy-on-commit for worklets, gestures, and spread props to avoid background-side mutation.
- Prevent _execId churn for stable references, reducing redundant native patches.
- Fix gesture removal cleanup when removed from spread props.
- Add regression tests for execId churn and gesture cleanup.
- Add changeset for @lynx-js/react-runtime.
@f0rdream
Copy link
Copy Markdown
Collaborator

LGTM

@Yradex Yradex force-pushed the wt/mts-support-use-callback-20260202 branch from 6aa232b to 0ec04e7 Compare April 22, 2026 08:18
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
packages/react/runtime/__test__/snapshot/gesture.test.jsx (1)

1215-1221: Optional: fixture toJSON patterns are inconsistent across this file.

Most updated fixtures now use toJSON: function() { return { ...this, __isSerialized: true }; }, but the two test-scoped createGesture helpers (Lines 1215-1221, 1309-1315) still strip toJSON via const { toJSON, ...rest } = this. Both work, but the mixed style makes it harder to scan; consider unifying to one pattern. Purely cosmetic.

Also applies to: 1309-1315

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react/runtime/__test__/snapshot/gesture.test.jsx` around lines 1215
- 1221, Update the test-scoped createGesture helpers so their toJSON
implementations use the unified pattern used elsewhere: replace the method that
destructures off toJSON (currently written as toJSON() { const { toJSON, ...rest
} = this; return { ...rest, __isSerialized: true }; }) with the concise form
toJSON: function() { return { ...this, __isSerialized: true }; } in both
createGesture helpers (the helper that defines toJSON at the top and the second
test-scoped createGesture), ensuring both fixtures use the same style for
consistency.
packages/react/runtime/src/snapshot/gesture/processGestureBagkround.ts (1)

49-56: Optional: document the call-site invariant on attachCommittedSerializer.

Object.assign(gesture, { serialize, toJSON }) mutates the input. Current callers (Lines 72 and 93) always pass a freshly spread copy, so this is safe — but nothing in the type signature enforces it, and a future caller could accidentally pass the original and leak the committed serializer onto the background-cached gesture, reintroducing the very mutation churn this PR is trying to eliminate. A one-line comment like // callers MUST pass a fresh copy; this mutates the argument would harden the contract without changing behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react/runtime/src/snapshot/gesture/processGestureBagkround.ts`
around lines 49 - 56, Add a one-line invariant comment above
attachCommittedSerializer documenting that it mutates its argument and therefore
callers MUST pass a fresh/shallow-copied gesture; reference
attachCommittedSerializer and serializeCommittedGesture in the comment to make
it clear this function assigns serialize/toJSON onto the passed-in gesture and
that callers must not pass shared background-cached gesture objects.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/react/runtime/__test__/snapshot/gesture.test.jsx`:
- Around line 1215-1221: Update the test-scoped createGesture helpers so their
toJSON implementations use the unified pattern used elsewhere: replace the
method that destructures off toJSON (currently written as toJSON() { const {
toJSON, ...rest } = this; return { ...rest, __isSerialized: true }; }) with the
concise form toJSON: function() { return { ...this, __isSerialized: true }; } in
both createGesture helpers (the helper that defines toJSON at the top and the
second test-scoped createGesture), ensuring both fixtures use the same style for
consistency.

In `@packages/react/runtime/src/snapshot/gesture/processGestureBagkround.ts`:
- Around line 49-56: Add a one-line invariant comment above
attachCommittedSerializer documenting that it mutates its argument and therefore
callers MUST pass a fresh/shallow-copied gesture; reference
attachCommittedSerializer and serializeCommittedGesture in the comment to make
it clear this function assigns serialize/toJSON onto the passed-in gesture and
that callers must not pass shared background-cached gesture objects.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1a210967-c23f-4248-adba-388abc9f41a0

📥 Commits

Reviewing files that changed from the base of the PR and between 6aa232b and 0ec04e7.

📒 Files selected for processing (9)
  • .changeset/fix-react-runtime-execid-churn.md
  • packages/lynx/gesture-runtime/src/baseGesture.ts
  • packages/lynx/gesture-runtime/src/composition.ts
  • packages/react/runtime/__test__/snapshot/gesture.test.jsx
  • packages/react/runtime/__test__/snapshot/gesture/prepareGestureForCommit.test.js
  • packages/react/runtime/__test__/snapshot/gesture/processGesture.test.ts
  • packages/react/runtime/__test__/snapshot/mtf-execid-churn.test.jsx
  • packages/react/runtime/src/snapshot/gesture/processGesture.ts
  • packages/react/runtime/src/snapshot/gesture/processGestureBagkround.ts
✅ Files skipped from review due to trivial changes (1)
  • .changeset/fix-react-runtime-execid-churn.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/lynx/gesture-runtime/src/composition.ts
  • packages/lynx/gesture-runtime/src/baseGesture.ts
  • packages/react/runtime/test/snapshot/mtf-execid-churn.test.jsx

@Yradex Yradex merged commit d8cdd7c into lynx-family:main Apr 22, 2026
49 of 50 checks passed
@Yradex Yradex deleted the wt/mts-support-use-callback-20260202 branch April 22, 2026 11:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants